package org.NooLab.glue.instances;

import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

import org.NooLab.utilities.files.DFutils;
import org.NooLab.utilities.logging.PrintLog;
import org.NooLab.utilities.net.GUID;


import org.NooLab.glue.components.DataSenderIntf;
import org.NooLab.glue.components.MessageBoardFactory;
import org.NooLab.glue.components.MessageBoardFactoryProperties;
import org.NooLab.glue.components.pkg.TaskPackage;
import org.NooLab.glue.interfaces.ReceptorDigestIntf;
import org.NooLab.glue.msgframe.XRegistration;
import org.NooLab.glue.net.ComBoxIntf;
import org.NooLab.glue.storage.MsgBoardDataStorage;



/**
 * 
 * 
 * this manages storing, sending, retrying, etc on its own,
 * even after restart 
 * 
 * it is not used "directly", but through the interface, which provides only high-level semantics
 * 
 * once the "client" has provided the package, the client may forget about it
 * 
 * this object hosts a collection, whose items are result returns...  
 * 
 *  
 * ParticipantsDeliveryPadIntf :: resetDeliveryWaiting
 * 
 */
public class ParticipantsDeliveryPad implements ParticipantsDeliveryPadIntf{
	// =================================

	// object references ..............

	MessageBoardFactory msgboardFactory ; 
	
	MsgBoardDataStorage storage;
	
	ComBoxIntf comBox ;
	
	XRegistration xRegistration ; 
	
	ReceptorDigestIntf taskPerformer;

	DeliveryObjects deliveryObjects ;
	
	// mapping transactionID -> resultSender, in order to be able to stop a specific sender process 
	Map<Long,Object> activeDeliveryProcesses = new HashMap<Long,Object>();
	
	// main variables / properties ....
	
	int checkingPeriodLength = 15000 ; // should be dependent on transport protocol and traffic ...
	
	String participantsRole ="" ;
	String specsID="" ;
	
	String persistencePath= "" ;
	
	// this is set through receiving the confirmation from the MessageBoard !!
	String recvSendConfirmation="" ;
	boolean dobLoaded=false;
	boolean deliveryCompleted=false;
	boolean enforcedStop = false;
	
	int targetPort = 7200 ;
	String targetHost = "localhost" ;
	
	String storagePath = "";
	String filename="" ;
	
	// simply GUID
	Map<String,Object> waitingItems = new HashMap<String,Object>();
	Vector<String> itemqueue = new Vector<String> ();
	
	// constants ......................
	
	

	// volatile variables .............
	DeliveryHandler deliveryHandler;
	
	Vector<DeliveryObject> sentDeliveries = new Vector<DeliveryObject>() ; 
	
	// helper objects .................
	 
	PrintLog out = new PrintLog(2,true);
	DFutils fileutil = new DFutils();

	// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
	/**
	 * 
	 */
	public ParticipantsDeliveryPad( MessageBoardFactory mbf, String role, String specsID, PrintLog outprn){
		
		msgboardFactory = mbf;
		
		storage = msgboardFactory.getMessageBoardStorage() ;
		
		participantsRole = role;
		this.specsID = specsID;
		
		// for tricky callback: the participant delegates the sending out of its control,
		// but it has to know, whether it was successful or not (in order to leave busy state)
		taskPerformer = msgboardFactory.getParticipantReference().getMrd();
		
		// sth like an enriched map
		deliveryObjects = new DeliveryObjects() ;
		
		if (outprn!=null){
			out = outprn;
		}
		
		deliveryHandler = new DeliveryHandler( );
		
		
		comBox = msgboardFactory.getComBoxFactory().getComBox() ;
		
		xRegistration = msgboardFactory.getRegisteredInstances().get(0) ;
		
		createFilename();
		open();
		dobLoaded=true;
	}

 
	
	/**
	 * 
	 * creates a new GUID and puts it into the queue
	 */
	public String createNotificationForMessageBoard( XRegistration xreg) {
		 
		String guidStr="";
		
		xRegistration = xreg;
		
		guidStr = GUID.randomvalue() ;
		itemqueue.add(guidStr);
		
		save();
		
		return guidStr;
	}
	
	 
	/**
	 * used by MessageBoardParticipant.sendResultPackage() (as "deliveryPad.put()") 
	 */
	public int put(  String deliveryReceipt, long transactID, TaskPackage resultPackage) {
		
		DeliveryObject dobj;
		// Map<String,Object> waitingItems
		
		dobj = new DeliveryObject();
		
		 
		dobj.deliveryReceipt = deliveryReceipt;
		dobj.transactionID = transactID ;
		dobj.dpackage = resultPackage;
		// e.g.: D:/dev/java/data/test/mb/transactData/receptor/854c4915-3032-476e-bdb7-c13d0575cb69/~tdata_collection.lst
		deliveryObjects.save( filename );
		deliveryObjects.add(dobj);
		// this is the FiFo queue for objects/packages to be sent, which is taken by by DeliveryHandler (down below)
		
		return deliveryObjects.size() ; 
	}

	public void digestingConfirmation( long transactionID ){
		 
		int  n;
		 
		
		// pause the loop in the handler from further deliveries, while we are working here...
		deliveryHandler.setHandlerPaused(true);
			
			n = deliveryObjects.size();
			out.printErr(4, "handling_deliveryConfirmation in <ParticipantsDeliveryPad>, "+n+" deliveries are waiting...") ;
			
		/*
		 * that's not completely correct, since there could be many returns of results, or confirmations of
		 * returned results may overlap
		 */
		
		removeTransactionFromDeliveries( deliveryObjects, transactionID);
		//  sentDeliveries, transactionID);
		// TODO, care about sentDeliveries !!! remove it from there too!
		
		
		n = deliveryObjects.size();
		out.printErr(3, "after digesting confirmation, "+n+" deliveries are waiting...") ;
		
		deliveryHandler.setHandlerPaused(false);
		save();
	}
	
	private int removeTransactionFromDeliveries( DeliveryObjects deliveryObjects, long transactionID){
		int index;
		String guidStr;
		int result = -1;
		
		index = deliveryObjects.indexOfByTransactionID(transactionID);
						out.print( 4, ">>>   digesting confirmation sent by MessageBoard upon delivery of results (tid:"+ transactionID + ") ...");
						
		try{
			
			result = -3;
			
			if (index >= 0) {
				
				guidStr = deliveryObjects.get(index).getDeliveryReceipt();

				deliveryObjects.get(index).isWaiting = false;
				DeliveryObject dobj = deliveryObjects.get(index) ;
				dobj=null;
				deliveryObjects.remove(index);

				int p = itemqueue.indexOf(guidStr);
				if (p >= 0) {
					
					try{
						if (activeDeliveryProcesses.containsKey( transactionID )){
							
							ResultSender resultSender = (ResultSender) activeDeliveryProcesses.get( transactionID);
							
							if (resultSender!=null){
								resultSender.enforcedStop = true;
								delay(10);
							}
						}
						activeDeliveryProcesses.remove(transactionID) ;

					}catch(Exception e){}
					
					itemqueue.remove(p);
					result = 0;
				}

				// deliveryObjects.save( filename );
				save();

				// this still not being completely correct, these indicator need
				// their own collection list...
				// deliveryCompleted = true;
				
			}
		}catch(Exception e){
			result = -17;
		}

		return result ;
	}

	
	@Override
	public int removeDeliveryProcess(long transactID) {
		
		return removeTransactionFromDeliveries( deliveryObjects, transactID) ;
		
	}

	@Override
	public void resetDeliveryWaiting( long transactionID ) {
		ResultSender rs ;
		
		rs = (ResultSender) activeDeliveryProcesses.get( transactionID);
		rs.restartWaitCounter();
	}



	/**
	 * called on shutting the participant
	 * 
	 */
	public void close(){
		// stopping the handler
												out.print(3, "stopping the delivery handler... ") ;
		deliveryHandler.isRunning = false;
		enforcedStop = true;
		delay(100);
		 
		// saving the objects
		save();
		if (deliveryHandler!=null){
			deliveryHandler.stop();
		}
												out.print(3, "stopping the delivery handler succeeded. ") ;
	}


	private void createFilename(){
		String path;
		
		path = msgboardFactory.getConfigPath();
		
		path = storage.getHomePath();
		
		persistencePath = fileutil.createPath(path , "transactData/"+participantsRole.toLowerCase()+"/"+specsID+"/") ;
		
		filename = fileutil.createPath( persistencePath,"~tdata_collection.lst");
		
		// mb/transactData/receptor/~tdata_tid-338.obj
	}



	public void setRegistrationObject( XRegistration xreg ){
		xRegistration = xreg ;
	}
	
	protected void abc( DeliveryObject dobj){
		
		TaskPackage taskPackage = new TaskPackage ();
		
		taskPackage.setDataString("delivery") ;
		taskPackage.setTypeOfProceedings("confirmation") ;
		taskPackage.setStateFlag(-1); 
		
		taskPackage.setTransactionID(dobj.transactionID) ;
		
		// executing the callback in:     MessageBoardParticipant::handling_deliveryConfirmation()
		
		taskPerformer.performTask(taskPackage) ;
	}
	
	
	protected void open(){
		DeliveryObject dobj;
		
		try{
			// D:/dev/java/data/test/mb/transactData/source/~tdata_collection.lst
			if (fileutil.fileexists(filename)==false){
				return;
			}
			
			deliveryObjects = (DeliveryObjects)storage.readTransactionDataObj(filename) ;
			
			
			
			for (int i=0;i<deliveryObjects.size();i++){
				
				dobj = deliveryObjects.get(i) ;
				
				dobj.setSendingProcessRunning(false) ;
				dobj.setSendCounter(0) ;
			}
			
			
			if (deliveryObjects!=null){
				out.print(3, "opening hibernated delivery queue: "+deliveryObjects.size()+" objects loaded.");
			}

		}catch(Exception e){
			out.printErr(2, "Error while opening hibernated objects.\n\r"+"expected filename :"+filename+"\n\r" + e.getMessage()+"\n\r"+e.getStackTrace()  ) ;
		}
		
	}



	private void save(){
		
		storage.storeTransactionDataObj( deliveryObjects, filename) ;
		
	}


	private void send( long transactID, TaskPackage resultPackage ){
		  
		deliveryCompleted = false;
		enforcedStop = false;
		
		// TODO: this should use a secrecy ! in order to provide security against fakes,
		// identification of the returned confirmation is provided by the transaction ID
		// this will by "observed" by update() in MessageReceptor, http-sending is via "Post" and route /result
		if (xRegistration!=null){
												out.print(2,"sending transaction (tid:"+transactID+") from delivery pad...");
			xRegistration.sendResultReturn( transactID, resultPackage );
		}
		// sending back results needs a confirmation by the board
		// to check whether the message has arrived, if not, we have to 
		// save the results (and send it next time the MessageBoard is up and running)	
	}
	
	
	@SuppressWarnings("static-access")
	private void delay(int millis){
	
		try{
			Thread.currentThread().sleep(millis) ;
		}catch(Exception e){}
	}
	
	
	public void handlingSingleDelivery( long now, DeliveryObject dobj){
			
		int r;
		ResultSender resultSender ;
		
		
		// create a new sending thread process
		resultSender = new ResultSender( targetPort, dobj, "receptor's send process");
		
		activeDeliveryProcesses.put( dobj.transactionID, resultSender);
		r = resultSender.waitForDelivery();
		if (r<0){
			
		}else{
			
		}
	}


	class ResultSender implements Runnable{
		
		DataSenderIntf mbSender ;
		
		String xml;
		boolean timedOut;
		Thread regsendingThrd;
		int timeOut = 4000, resultsReturnPort;
		int periodLength = 800, z;
	
		DeliveryObject dobj;
		int count = 1, sendProcessActivation=0;
		boolean enforcedStop = false ;
		long transid;
		String description;
		Timer timer;
		TTask ttask;
		
		// see: MessageBoard's inner class ResultSender
		public ResultSender( int port, DeliveryObject dobj, String description){
			
			resultsReturnPort = port;
			this.dobj = dobj;
			
			this.transid = dobj.getTransactionID() ;
			regsendingThrd = new Thread( this,"RecResultSender-"+port);
			
			this.description = description;
	
			/*
			timer = new Timer(false);
			ttask = new TTask(description);
			timer.schedule(ttask, timeOut);
			*/
		}
	
		
		 
		public void run() {
			deliveryCompleted = false;
			send( dobj.getTransactionID(),  dobj.getDpackage() );
			//confirmedSender ?
		}
	
		public void restartWaitCounter(){
			z=0;
		}
		
		public int waitForDelivery() {
	
			int result = -1;
			int limit = 800; // x10 800 = 8sec ; 
			z = 1;
			enforcedStop = false;
			
			
												out.print(3,">>>   delivery (tid:"+dobj.getTransactionID()+") now put into the wires..."); 
			dobj.isWaiting = true;
			regsendingThrd.start();
			
			
			while ((deliveryCompleted == false) && (timedOut == false) && (enforcedStop==false)) {
				
				if (deliveryCompleted==false){
					deliveryCompleted = (recvSendConfirmation.contains(""+transid));
				}
				 
				timedOut = (z > limit); // 100 = approx. 10 seconds
				if ((z > 2) && (z % 50 == 0)) {
					if ((z < 55) && (z > 5)) {
						// out.print(3,"");
					}
	 
				}
				 
				try {
					if (deliveryCompleted == false) {
						delay(5) ;  
						// TODO: make this a field, an option, accessible acc. to size of message !!!!
						 
					}
	
				} catch (Exception e) {
				}
				delay(5);
				z++;
			}
			dobj.isWaiting = false;
			if ((enforcedStop) || (deliveryCompleted)){
				
				if (timer!=null){
					ttask.cancel() ;
					timer.cancel();
					timer.purge() ;
					  
				}
			}
										out.print(4,">>>   waiting loop interrupted.");
			if (deliveryCompleted == true) {
				result=0;
				out.print(3,">>>   delivery succeeded, confirmation got!");
				
			}else{
				out.print(3,">>>DeliveryPad : delivery.");
				if (dobj!=null){
					
					if (dobj.getSendCounter()<=10){
						dobj.setSendingProcessRunning(false);
					}else{
						// we have to release the busy state of the receptor again !!
						abc(dobj);
					}
					dobj.incrementSendCounter();
				}
			}
			return result;
		}
		
		
		public void requestStop() {
			enforcedStop = true;
			 
			delay(100);
			try {
				// regsendingThrd.join();
			} catch (Exception e) {
			}
		}
		
		public TTask getTtask() {
			return ttask;
		}

		class TTask extends TimerTask {
			String description;
	
			public TTask(String description) {
				this.description = description;
			}
	
			public void run() {
	
				out.print(4, "Requesting stop of " + description + "...");
				requestStop();
			}
			
			public void stopTimer(){
				try {
					// Thread.currentThread().join();
					this.cancel();
				} catch (Exception e) {
				}
			}
	
		}
	
	} // ResultSender



	/**
	 * 
	 * this one runs all the time
	 * 
	 */
	class DeliveryHandler implements Runnable{
	
		boolean handlerPaused = false;
		
		boolean isRunning = false;
		boolean isWorking = false;
		 
		int protocolID = -1 ;
		
		private Thread thrd;
		 
		
		public DeliveryHandler(  ){
		  
			protocolID = msgboardFactory.getFactoryProperties().getProtocolId() ;
			
			thrd = new Thread(this,"DeliveryHandler");
			thrd.setName("DeliveryHandler");
			thrd.start() ;
		}
	
		 
	    public void stop(){
	    	
	    	try {
				thrd.join();
			} catch (InterruptedException e) {
			}
	    }
		 
		@SuppressWarnings({  "unused" })
		public void run() {
			isRunning = true;
			int nid = 0,n ,z=0 ;
			long now, cycleStartedAt, lastCheckingTime;
			DeliveryObject dobj ;
			boolean timeOk;
			
			
			
			try{
	
				while ( isRunning ) {
					cycleStartedAt = System.currentTimeMillis() ;
					if (enforcedStop){
						break;
					}
					if ((isWorking == false) && (handlerPaused==false)) {
						z++; if (z>100000){z=1;}
						delay(1);
													// out.print(2,"DeliveryHandler ...  " );
						if ((deliveryObjects.size() > 0) && (xRegistration!=null) && (dobLoaded)){
							
							n = deliveryObjects.size();
												    if (n>0){
												    	out.print(4,">>>   number of deliveries waiting for instance id="+xRegistration.getInstanceID()+": "+n);
												    }
							isWorking = true;	
							nid=0; 
							
							
							while ( (deliveryObjects.size()>0)  &&
									(enforcedStop==false) && (isRunning) && 
									(deliveryObjects.candicount()>0)) {
								
								now = System.currentTimeMillis();
								
								dobj = deliveryObjects.get(0) ;
								
								lastCheckingTime = dobj.getLastEventTime();
								timeOk = ((System.currentTimeMillis() - lastCheckingTime) > checkingPeriodLength) || ( dobj.getSendCounter()==0) ;
											// TODO: put length of period to option field
								
								boolean asyncTransport = (xRegistration.isInstanceConnectionConfirmed()) ||
											 (protocolID <= MessageBoardFactoryProperties.protocolFTP);
											 		
								if (dobj.isWaiting){
									if ((System.currentTimeMillis() - lastCheckingTime) > 1.3*checkingPeriodLength){
										deliveryObjects.remove(0);
										deliveryObjects.add(dobj);
										dobj.isWaiting=false;
										dobj.sendCounter = dobj.getSendCounter()+1;
										continue;
									}
								}
								if ( (timeOk) && (dobj.isSendingProcessRunning()==false) && (dobj.isWaiting==false)){
									if (asyncTransport){
										
									}
									
									// call the treatment on the particular notifications
									// this treatment decides, whether to send, and also
									// it removes items from the list
									dobj.setSendingProcessRunning(true);
									
									// TODO: ??? put this dobj into a list of objects currently being sent to the MessageBoard
									// sentDeliveries.add(dobj) ;
									
												out.print(4,">>>   delivery selected, now sending id="+dobj.transactionID+" to BOARD..." );
												out.print(5,"delivery handling dobj of tid = "+ dobj.transactionID);
									// for handling this notification, we will open a further thread
									handlingSingleDelivery(now, dobj);
									
									dobj.setLastEventTime( System.currentTimeMillis()) ;
								} // timeOk, isWaiting=false ?
								delay(10); 
								if ((deliveryObjects.size()>0) && (z%100==0)){
									out.print(4,"size of delivery queue : "+deliveryObjects.size() );
								}
							}
							
						
						} // size() > 0 ?
						isWorking = false;
					}  else {
						 delay(5);
					}
					
				} // while ...
				
			}catch(Exception e){
				
			}finally{
				isRunning = false;
				isWorking = false;
			}
		}
		
		public void enforcedStop(){
			
			if (thrd != null){
				try {
					thrd.interrupt();
				} catch (Exception e) {
				}
				isRunning = false;
				isWorking = false;
			}
		}
	
		public boolean isRunning() {
			return isRunning;
		}


		public void setHandlerPaused(boolean handlerPaused) {
			this.handlerPaused = handlerPaused;
		}
		
	} // inner class DeliveryHandler



	public int getCheckingPeriodLength() {
		return checkingPeriodLength;
	}



	public void setCheckingPeriodLength(int checkingPeriodLength) {
		this.checkingPeriodLength = checkingPeriodLength;
	}



	public boolean isEnforcedStop() {
		return enforcedStop;
	}



	public void setEnforcedStop(boolean enforcedStop) {
		this.enforcedStop = enforcedStop;
	}



	public boolean isDeliveryCompleted() {
		return deliveryCompleted;
	}



	public void setDeliveryCompleted(boolean deliveryCompleted) {
		this.deliveryCompleted = deliveryCompleted;
	}
	
}

 















